package fr.univlille1.ftp;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.io.File;

public class FTPRequest extends Thread {

	private Socket socket;
	private File currentDir;
	private String user;
	private char transfertType;
	private int clientPort;
	private InetAddress clientAddress;
	private boolean passiveMode;
	private ServerSocket nextSocket;
	private InetAddress localHost;
	private int startingOffset;

	public FTPRequest(Socket s, String localAddr) throws UnknownHostException{
		this.socket = s;
		this.currentDir = Server.getBaseDir();
		this.user = "";
		this.transfertType = 'a';
		this.passiveMode = false;
		this.nextSocket = null;
		this.localHost = InetAddress.getByName(localAddr);
		this.startingOffset = 0;
	}

	public void run(){
		try{
			BufferedReader input = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			PrintWriter output = new PrintWriter(this.socket.getOutputStream());
			System.out.println("Un client est connecté");
			output.write("220 Welcome!\n");
			output.flush();
			String message = "";
			while(!message.equals("QUIT")){
				message = input.readLine();
				this.processRequest(message, output);
			}
			this.socket.close();
		}catch(IOException e){
			e.printStackTrace();
		}
	}

	private void processRequest(String message, PrintWriter output){
		if(message.matches("USER \\w*")){
			System.out.println("Commande \"USER\" reconnue");
			this.processUser(message, output);
		}else if(message.matches("APPE .*")){
			System.out.println("Commande \"APPE\" reconnue");
			this.processAppe(message, output);			
		}else if(message.matches("PASS .*")){
			System.out.println("Commande \"PASS\" reconnue");
			this.processPass(message, output);
		}else if(message.matches("LIST") || message.matches("LIST .*")){
			System.out.println("Commande \"LIST\" reconnue");
			this.processList(message, output);
		}else if(message.matches("SYST")){
			System.out.println("Commande \"SYST\" reconnue");
			this.processSyst(message, output);
		}else if(message.matches("REST .*")){
			System.out.println("Commande \"REST\" reconnue");
			this.processRest(message, output);
		}else if(message.matches("RETR .*")){
			System.out.println("Commande \"RETR\" reconnue");
			this.processRetr(message, output);
		}else if(message.matches("MKD .*")){
			System.out.println("Commande \"MKD\" reconnue");
			this.processMkd(message, output);
		}else if(message.matches("RMD .*")){
			System.out.println("Commande \"RMD\" reconnue");
			this.processRmd(message, output);
		}else if(message.matches("STOR .*")){
			System.out.println("Commande \"STOR\" reconnue");
			this.processStor(message, output);
		}else if(message.matches("QUIT")){
			System.out.println("Commande \"QUIT\" reconnue");
			this.processQuit(message, output);
		}else if(message.matches("PWD")){
			System.out.println("Commande \"PWD\" reconnue");
			this.processPwd(message, output);
		}else if(message.matches("CWD .*")){
			System.out.println("Commande \"CWD\" reconnue");
			this.processCwd(message, output);
		}else if(message.matches("CDUP")){
			System.out.println("Commande \"CDUP\" reconnue");
			this.processCdup(message, output);
		}else if(message.matches("FEAT")){
			System.out.println("Commande \"FEAT\" reconnue");
			this.processFeat(message, output);
		}else if(message.matches("PASV")){
			System.out.println("Commande \"PASV\" reconnue");
			this.processPasv(message, output);
		}else if(message.matches("TYPE \\w")){
			System.out.println("Commande \"TYPE\" reconnue");
			this.processType(message, output);
		}else if(message.matches("PORT \\d*,\\d*,\\d*,\\d*,\\d*,\\d*")){
			System.out.println("Commande \"PORT\" reconnue");
			this.processPort(message, output);
		}else if(message.matches("SIZE .*")){
			System.out.println("Commande \"SIZE\" reconnue");
			this.processSize(message, output);
		}else if(message.matches("DELE .*")){
			System.out.println("Commande \"DELE\" reconnue");
			this.processDele(message, output);
		}else if(message.matches("MDTM .*")){
			System.out.println("Commande \"MDTM\" reconnue");
			this.processMdtm(message, output);
		}else{
			System.out.println("Dafuq?");
			this.processPEBKAC(message, output);
		}
	}
	
	private void processAppe(String message, PrintWriter output){
		File toBeDownloaded = null;
		String path = message.substring(5);
		try {
			// Recherche du chemin du fichier à écrire
			if(path.substring(0,1).equals("/")){
				toBeDownloaded = new File(path);
			}else{
				toBeDownloaded = new File(this.currentDir.getCanonicalFile()+"/"+path);
			}
			// Test des droits d'accès et/ou création du fichier
			if(toBeDownloaded.exists()){
				if(!toBeDownloaded.canWrite()){
					output.write("552 Fichier verrouillé\n");
					output.flush();
					return;
				}
			}else if(!toBeDownloaded.getParentFile().canWrite()){
				output.write("553 Le contenu du dossier ne peut être modifié\n");
				output.flush();
				return;
			}else{
				if(!toBeDownloaded.createNewFile()){
					output.write("554 Le fichier n'a pas pu être créé\n");
					output.flush();
					return;
				}
			}
		}catch(IOException e){
			output.write("555 Le système de fichier n'a pas aimé un truc\n");
			output.flush();
			return;
		}
		// Lancement du thread
		Thread t = null;
		if(this.passiveMode){
			t = new FTPTransferer(this.transfertType, this.nextSocket, toBeDownloaded, message, FTPCommands.STOR, 0, output);
		}else{
			t = new FTPTransferer(this.transfertType, this.clientPort, toBeDownloaded, message, FTPCommands.STOR, this.clientAddress, 0, output);
		}
		t.start();
		output.write("150 Stream Opened\n");
		output.flush();
		return;
	}

	private void processCdup(String message, PrintWriter output){
		// TODO
		output.write("502 Not implemented\n");
		output.flush();
	}

	private void processCwd(String message, PrintWriter output){
		try{
			String newDir = message.substring(message.indexOf(" ")+1);
			if(!newDir.startsWith("/")){
				newDir = this.currentDir.getCanonicalPath()+"/"+newDir;
			}
			File tmp = new File(newDir);
			if(tmp.exists()){
				this.currentDir = tmp;
				output.write("250 New pwd is "+tmp.getCanonicalPath()+"\n");
			}else{
				output.write("550 I would say 404 but we are FTP, not HTTP...\n");
			}
			output.flush();
		}catch(IOException e){
			output.write("550 Java's Filesystem fucked it up again!\n");
			output.flush();
		}
	}
	
	private void processDele(String message, PrintWriter output){
		File toDelete=null;
		String path = message.substring(5);
		if(path.substring(0,1).equals("/")){
			toDelete = new File(path);
		}else{
			try{
				toDelete = new File(this.currentDir.getCanonicalFile()+"/"+path);
			}catch(IOException e){
				e.printStackTrace();
			}
		}
		if(!toDelete.exists()){
			output.write("550 I would say 404 but we are FTP, not HTTP...\n");
			output.flush();
			return;
		}
		toDelete.delete();
		output.write("252 File deleted\n");
		output.flush();
	}

	private void processFeat(String message, PrintWriter output){
		FTPCommands[] test = FTPCommands.values();
		output.write("110 "+test[0].toString());
		for(int i=1; i<test.length; i++){
			output.write(","+test[i].name());
		}
		output.write("\n200 Done\n");
		output.flush();
	}

	private void processList(String message, PrintWriter output){
		/*
		if(this.currentDir.list().length == 0){
			output.write("250 Ce ne sont pas ces droides que vous recherchez\n");
			output.flush();
			return;
		}
		*/
		File dir = null;
		if(message.length()>4){
			dir = new File(message.substring(5));			
		}else{
			dir = this.currentDir;
		}
		if(dir.isFile()){
			output.write("550 Can't list a file dumbass!\n");
			output.flush();
			return;
		}
		Thread t = null;
		if(this.passiveMode){
			t = new FTPTransferer(this.transfertType, this.nextSocket, this.currentDir, message, FTPCommands.LIST, 0, output);
		}else{
			t = new FTPTransferer(this.transfertType, this.clientPort, this.currentDir, message, FTPCommands.LIST, this.clientAddress, 0, output);
		}
		t.start();
		output.write("120 Opening stream\n");
		output.flush();
		
	}

	private void processMdtm(String message, PrintWriter output){
		String filepath = message.substring(message.indexOf("/"));
		File file = new File(filepath);
		DateFormat dateformater = new SimpleDateFormat("dd-MM-YYYY HH:mm:ss:SSS");
		output.write("220 "+dateformater.format(file.lastModified())+"\n");
		output.flush();
	}
	
	private void processMkd(String message, PrintWriter output){
		File toBeCreated = null;
		String path = message.substring(4);
		if(path.substring(0,1).equals("/")){
			toBeCreated = new File(path);
		}else{
			try{
				toBeCreated = new File(this.currentDir.getCanonicalFile()+"/"+path);
			}catch(IOException e){
				e.printStackTrace();
			}
		}
		if(toBeCreated.exists()){
			output.write("551 That directory already exists\n");
			output.flush();
			return;
		}
		toBeCreated.mkdir();
		output.write("251 directory successfully made\n");
		output.flush();
		
	}
	
	private void processPass(String message, PrintWriter output){
		if(this.user.equals("")){
			output.write("503 Who are you?");
			output.flush();
		}else{
			output.write("230 Welcome random citizen! I don't even give a fuck about who you are!\n");
			output.flush();
		}
	}

	private void processPasv(String message, PrintWriter output) {
		this.passiveMode = true;
		int first, second, third, fourth;
		first = this.localHost.getAddress()[0];
		if(first<0) first+=127;
		second= this.localHost.getAddress()[1];
		if(second<0) second += 127;
		third = this.localHost.getAddress()[2];
		if(third<0) third+=127;
		fourth = this.localHost.getAddress()[3];
		if(fourth<0) fourth+=127;

		try{
			this.nextSocket = new ServerSocket();
			this.nextSocket.bind(null);
			output.write("227 Entering passive mode (" 
					+ first +","
					+ second +","
					+ third+","
					+ fourth +","
					+ (this.nextSocket.getLocalPort()>>8)+","+
					+ (this.nextSocket.getLocalPort()&0xFF)+").\n");
			output.flush();
		}catch(IOException e){
			e.printStackTrace();
		}
	}

	private void processPort(String message, PrintWriter output) {
		message = message.substring(message.indexOf(' ')+1);
		byte[] tmpAddrByte = new byte[4];
		this.passiveMode = false;
		InetAddress tmpAddr = null;
		String[] splitted = message.split(",");
		int tmpPort = (Integer.parseInt(splitted[4])<<8)+Integer.parseInt(splitted[5]);
		tmpAddrByte[0] = Byte.parseByte(splitted[0]);
		tmpAddrByte[1] = Byte.parseByte(splitted[1]);
		tmpAddrByte[2] = Byte.parseByte(splitted[2]);
		tmpAddrByte[3] = Byte.parseByte(splitted[3]);
		try {
			tmpAddr = InetAddress.getByAddress(tmpAddrByte);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		this.clientPort = tmpPort;
		this.clientAddress = tmpAddr;
		if(tmpAddr == null){
			output.write("520 I did not understand your address :/ "+tmpAddrByte);
		}else{
			output.write("200 Ok\n");
		}
		output.flush();
	}

	private void processPwd(String message, PrintWriter output){
		try{
			output.write("212 "+this.currentDir.getCanonicalPath()+"\n");
			output.flush();
		}catch(IOException e){
			output.write("550 Java really ain't do it well...\n");
			output.flush();
		}
	}

	private void processQuit(String message, PrintWriter output){
		output.write("221 See you soon!\n");
		output.flush();
	}

	private void processRest(String message, PrintWriter output){
		this.startingOffset = Integer.parseInt(message.substring(5));
		output.write("250 Understood\n");
		output.flush();
	}

	private void processRetr(String message, PrintWriter output){
		File toBeUploaded = null;
		String path = message.substring(5);
		if(path.substring(0,1).equals("/")){
			toBeUploaded = new File(path);
		}else{
			try{
				toBeUploaded = new File(this.currentDir.getCanonicalFile()+"/"+path);
			}catch(IOException e){
				e.printStackTrace();
			}
		}
		if(!toBeUploaded.exists()){
			output.write("550 I would say 404 but we are FTP, not HTTP...\n");
			output.flush();
			return;
		}
		Thread t = null;
		if(this.passiveMode){
			t = new FTPTransferer(this.transfertType, this.nextSocket, toBeUploaded, message, FTPCommands.RETR, this.startingOffset, output);
		}else{
			t = new FTPTransferer(this.transfertType, this.clientPort, toBeUploaded, message, FTPCommands.RETR, this.clientAddress, this.startingOffset, output);
		}
		t.start();
		output.write("150 Stream Opened\n");
		output.flush();
	}
	
	private void processRmd(String message, PrintWriter output){
		File toDelete=null;
		String path = message.substring(4);
		if(path.substring(0,1).equals("/")){
			toDelete = new File(path);
		}else{
			try{
				toDelete = new File(this.currentDir.getCanonicalFile()+"/"+path);
			}catch(IOException e){
				e.printStackTrace();
			}
		}
		if(!toDelete.exists()){
			output.write("550 I would say 404 but we are FTP, not HTTP...\n");
			output.flush();
			return;
		}
		toDelete.delete();
		output.write("253 Directory deleted\n");
		output.flush();
	}

	private void processSize(String message, PrintWriter output){
		String filepath = message.substring(message.indexOf("/"));
		File file = new File(filepath);
		output.write("210 "+file.length()+"\n");
		output.flush();
		System.out.println("J'ai reçu la commande : "+file);
	}

	private void processStor(String message, PrintWriter output){
		File toBeDownloaded = null;
		String path = message.substring(5);
		try {
			// Recherche du chemin du fichier à écrire
			if(path.substring(0,1).equals("/")){
				toBeDownloaded = new File(path);
			}else{
				toBeDownloaded = new File(this.currentDir.getCanonicalFile()+"/"+path);
			}
			// Test des droits d'accès et/ou création du fichier
			if(toBeDownloaded.exists()){
				if(!toBeDownloaded.canWrite()){
					output.write("552 Fichier verrouillé\n");
					output.flush();
					return;
				}else{
					FileWriter fw = new FileWriter(toBeDownloaded.getCanonicalPath(), false);
					fw.write("");
					fw.close();
				}
			}else if(!toBeDownloaded.getParentFile().canWrite()){
				output.write("553 Le contenu du dossier ne peut être modifié\n");
				output.flush();
				return;
			}else{
				if(!toBeDownloaded.createNewFile()){
					output.write("554 Le fichier n'a pas pu être créé\n");
					output.flush();
					return;
				}
			}
		}catch(IOException e){
			output.write("555 Le système de fichier n'a pas aimé un truc\n");
			output.flush();
			return;
		}
		// Lancement du thread
		Thread t = null;
		if(this.passiveMode){
			t = new FTPTransferer(this.transfertType, this.nextSocket, toBeDownloaded, message, FTPCommands.STOR, 0, output);
		}else{
			t = new FTPTransferer(this.transfertType, this.clientPort, toBeDownloaded, message, FTPCommands.STOR, this.clientAddress, 0, output);
		}
		t.start();
		output.write("150 Stream Opened\n");
		output.flush();
		return;
	}

	private void processSyst(String message, PrintWriter output){
		// Used ftp.univ-lille1.fr's response
		output.write("215 UNIX Type: L8\n");
		output.flush();
	}

	private void processType(String message, PrintWriter output) {
		char tmp = message.charAt(5);
		if(tmp=='A' || tmp == 'I'){
			this.transfertType = tmp;
			output.write("220 Ok\n");			
		}else{
			output.write("501 I don't know what did you mean...\n");
		}
		output.flush();
	}

	private void processUser(String message, PrintWriter output){
		this.user = message.substring(5);
		output.write("331 Hi "+this.user+"!\n");
		output.flush();
	}

	private void processPEBKAC(String message, PrintWriter output){
		System.out.println("J'ai reçu la commande suivante : " + message);
		output.write("502 Je n'ai pas compris votre commande\n");
		output.flush();
	}

}
